#include "InterOpMetaDataExporter.h"
#include "InterOpUtils.h"

#include <SPAIopInputPSReference.h>
#include <SPAIopPartDocument.h>
#include <SPAIopAcisPartImporter.h>
#include <SPAIopPolicy.h>
#include <SPAIopPartImportResult.h>
#include <SPAIopInputPSInstance.h>
#include <SPAIopPSReferenceMiscData.h>
#include <SPAIopInputPSMissingFileIter.h>

#include <nlohmann/json.hpp>
#include "body.hxx"
#include "lump.hxx"
#include "shell.hxx"
#include "subshell.hxx"
#include "face.hxx"
#include "wire.hxx"
#include "loop.hxx"
#include "coedge.hxx"
#include "edge.hxx"
#include "kernapi.hxx"
#include <fstream>
#include "queryapi.hxx"
#include "mprop.hxx"
#include "common/logger.h"

#include <api_create_global_mesh.hxx>
#include <idx_mm.hxx>
#include <get_top.hxx>
#include <fct_utl.hxx>

using json = nlohmann::json;

AMCAX_EXCHANGE_NAMESPACE_BEGIN
namespace
{
    // Volume.
    double m_volume = .0;
    // Area.
    double m_area = .0;
    // Vertexes.
    std::list<InterOp::Point3D> m_vertexes;
    // Triangles.
    std::list<std::list<InterOp::Point3D>> m_triangles;
    // Input Facet options.
    InterOp::FacetOptions m_inputFacetOptions;

    void DumpEdge(ENTITY *entity, json &jsonData)
    {
        if (entity == nullptr || !is_EDGE(entity))
        {
            return;
        }
        auto edge = static_cast<EDGE *>(entity);
        auto startPoint = edge->start_pos();
        auto endPoint = edge->end_pos();
        jsonData["vertexes"] = json::array({{startPoint.x(), startPoint.y(), startPoint.z()},
                                            {endPoint.x(), endPoint.y(), endPoint.z()}});
    }

    void DumpCoedge(ENTITY *entity, json &jsonData)
    {
        if (entity == nullptr || !is_COEDGE(entity))
        {
            return;
        }
        auto coedge = dynamic_cast<COEDGE *>(entity);
        auto edge = coedge->edge();
        if (!edge)
        {
            return;
        }
        DumpEdge(edge, jsonData);
    }

    void DumpLoop(ENTITY *entity, json &jsonData)
    {
        if (entity == nullptr || !is_LOOP(entity))
        {
            return;
        }
        ENTITY_LIST child_list;
        api_get_coedges(entity, child_list);
        for (auto child : child_list)
        {
            json coedgeJson = json::object();
            DumpCoedge(child, coedgeJson);
            if(!coedgeJson.empty())
            {
                jsonData["edges"].push_back(coedgeJson);
            }
        }
    }

    void DumpWire(ENTITY *entity, json &jsonData)
    {
        if (entity == nullptr || !is_WIRE(entity))
        {
            return;
        }
        ENTITY_LIST child_list;
        api_get_coedges(entity, child_list);
        for (auto child : child_list)
        {
            json coedgeJson = json::object();
            DumpCoedge(child, coedgeJson);
            if(!coedgeJson.empty())
            {
                jsonData["edges"].push_back(coedgeJson);
            }
        }
    }

    void DumpFace(ENTITY *entity, json &jsonData)
    {
        if (entity == nullptr || !is_FACE(entity))
        {
            return;
        }

        ENTITY_LIST childList;
        api_get_loops(entity, childList);
        for (auto child : childList)
        {
            json loopJson = json::object();
            DumpLoop(child, loopJson);
            if(!loopJson.empty())
            {
                jsonData["loops"].push_back(loopJson);
            }
        }
    }

    void DumpSubShell(ENTITY *entity, json &jsonData)
    {
        if (entity == nullptr || !is_SUBSHELL(entity))
        {
            return;
        }

        auto subshell = dynamic_cast<SUBSHELL *>(entity);
        SUBSHELL *subshellChild = subshell->child();
        auto *face = subshell->face_list();
        auto *wire = subshell->wire_list();
        if (subshellChild)
        {
            while (subshellChild)
            {
                json subShellJson = json::object();
                DumpSubShell(subshellChild, subShellJson);
                if(!subShellJson.empty())
                {
                    jsonData["subshells"].push_back(subShellJson);
                }
                subshellChild = subshellChild->sibling();
            }
        }
        else if (face)
        {
            while (face->next_in_list() != NULL)
            {
                json faceJson = json::object();
                DumpFace(face, faceJson);
                if(!faceJson.empty())
                {
                    jsonData["faces"].push_back(faceJson);
                }
                face = face->next_in_list();
            }
        }
        else if (wire)
        {
            while (wire->next_in_list() != NULL)
            {
                json wireJson = json::object();
                DumpWire(wire, wireJson);
                if(!wireJson.empty())
                {
                    jsonData["wires"].push_back(wireJson);
                }
                wire = wire->next_in_list();
            }
        }
    }

    void DumpShell(ENTITY *entity, json &jsonData)
    {
        if (entity == nullptr || !is_SHELL(entity))
        {
            return;
        }

        auto shell = dynamic_cast<SHELL *>(entity);
        if (shell->face())
        {
            ENTITY_LIST childList;
            api_get_faces(shell, childList);
            for (auto child : childList)
            {
                json faceJson = json::object();
                DumpFace(child, faceJson);
                if(!faceJson.empty())
                {
                    jsonData["faces"].push_back(faceJson);
                }
            }
        }
        else if (shell->wire())
        {
            ENTITY_LIST childList;
            api_get_wires(shell, childList);
            for (auto child : childList)
            {
                json wireJson = json::object();
                DumpWire(child, wireJson);
                if(!wireJson.empty())
                {
                    jsonData["wires"].push_back(wireJson);
                }
            }
        }
        else if (shell->subshell())
        {
            for (SUBSHELL *subshell = shell->subshell(); subshell != NULL; subshell = subshell->sibling())
            {
                json subShellJson = json::object();
                DumpSubShell(subshell, subShellJson);
                if(!subShellJson.empty())
                {                
                    jsonData["subshells"].push_back(subShellJson);
                }
            }
        }
    }

    void DumpLump(ENTITY *entity, json &jsonData)
    {
        if (entity == nullptr || !is_LUMP(entity))
        {
            return;
        }

        ENTITY_LIST childList;
        api_get_shells(entity, childList);
        for (auto child : childList)
        {
            json shellJson = json::object();
            DumpShell(child, shellJson);
            if(!shellJson.empty())
            {
                jsonData["shells"].push_back(shellJson);
            }            
        }
    }

    void DumpBody(ENTITY *entity, const std::vector<SPAtransf> &transformList, json &jsonData)
    {
        if (entity == nullptr || !is_BODY(entity))
        {
            return;
        }

        ENTITY_LIST childList;
        api_get_lumps(entity, childList);
        for (auto child : childList)
        {
            if (!is_LUMP(child))
            {
                continue;
            }
            DumpLump(child, jsonData);
        }
    }

    void DumpBodyFacets(ENTITY *entity, INDEXED_MESH_MANAGER *imm)
    {
        auto fop = ACIS_NEW facet_options_precise();
        fop->set_polygon_type(m_inputFacetOptions.facetType == InterOp::FacetType::FACET_TYPE_TRIANGLE ? all_triangles : no_triangles_quad_tree);
        fop->set_max_edge_length(m_inputFacetOptions.maxEdgeLength);
        api_facet_entity(entity, fop);

        ENTITY_LIST bodyFaces;
        get_faces(entity, bodyFaces);
        bodyFaces.init();
        auto facetedFaces = static_cast<FACE *>(bodyFaces.next());
        while (facetedFaces != nullptr)
        {
            auto sequentialMesh = GetSequentialMesh(facetedFaces);
            if (sequentialMesh->get_type_of_mesh() != INDEXED_MESH_TYPE)
                sys_error(-1);

            auto indexMesh = static_cast<INDEXED_MESH *>(sequentialMesh);
            auto numP = indexMesh->get_num_polygon();
            for (int i = 0; i < numP; i++)
            {
                auto indexedPoly = indexMesh->get_polygon(i);
                std::list<InterOp::Point3D> polygonVertexes;
                for (int j = 0; j < indexedPoly->num_vertex(); j++)
                {
                    auto vertex = indexedPoly->get_vertex(j);
                    auto facetPosition = vertex->get_position();
                    // auto facetNormal = vertex->get_normal();
                    auto positon = InterOp::Point3D{facetPosition.x(), facetPosition.y(), facetPosition.z()};
                    polygonVertexes.push_back(positon);
                }
                // Add the polygon vertexes to the list of vertexes.
                m_vertexes.insert(m_vertexes.end(), polygonVertexes.begin(), polygonVertexes.end());
                // Add the polygon vertexes to the list of triangles.
                m_triangles.push_back(polygonVertexes);
            }
            facetedFaces = static_cast<FACE *>(bodyFaces.next());
        }
        ACIS_DELETE fop;
    }

    void DumpPartReference(const SPAIopInputPSReference &reference, const std::vector<SPAtransf> &transformList, std::unordered_map<int, ENTITY_LIST> &referenceEntityCache, json &jsonData)
    {
        if (!reference.IsPart())
        {
            return;
        }

        ENTITY_LIST entityList;
        if (referenceEntityCache.find(reference.GetUniqueID()) != referenceEntityCache.end())
        {
            entityList = referenceEntityCache[reference.GetUniqueID()];
        }
        else
        {
            SPAIopPartDocument acisPartDocument = reference.GetDocument();
            SPAIopAcisPartImporter partImporter;
            SPAIopPolicy policy;
            SPAIopPartImportResult importResult = partImporter.ImportWithPolicy(policy, acisPartDocument, entityList);
            referenceEntityCache[reference.GetUniqueID()] = entityList;
        }

        // Use indexed mesh manager to dump body facets.
        // Store the old mesh manager and set the new one
        MESH_MANAGER *oldMeshManager = 0;
        api_get_mesh_manager(oldMeshManager);
        auto imm = ACIS_NEW INDEXED_MESH_MANAGER();
        api_set_mesh_manager(imm);
        for (auto entity : entityList)
        {
            ENTITY *newEntity;
            api_copy_entity(entity, newEntity);
            for (auto &t : transformList)
            {
                api_transform_entity(newEntity, t);
                api_change_body_trans((BODY *)newEntity, nullptr);
            }

            // body volume.
            mass_props mp;
            mass_props_options mp_options;
            mp_options.set_req_rel_accy(0.0001);
            mp_options.set_level(mass_props_level::VOLUME_ONLY);
            auto massOutcome = api_body_mass_props((BODY *)entity, mp, &mp_options);
            m_volume += mp.get_volume();

            // body area.
            double area = 0.0;
            double est_rel_accy_achieved = 0.01;
            auto areaOutcome = api_ent_area(entity, 0.01, area, est_rel_accy_achieved);
            m_area += area;

            // body facets.
            DumpBodyFacets(newEntity, imm);

            DumpBody(entity, transformList, jsonData);
            api_del_entity(newEntity);
        }

        // Reset back the mesh manager to the old one.
        if (oldMeshManager)
        {
            api_set_mesh_manager(oldMeshManager);
        }
        ACIS_DELETE imm;
    }

    void DumpReference(const SPAIopInputPSReference &reference, const std::vector<SPAtransf> &transformList, SPAIopInputPSMissingFileIter &missingFileIter, json &jsonData)
    {
        std::unordered_map<int, ENTITY_LIST> referenceEntityCache;
        
        if(missingFileIter.ContainsPath(reference.GetStorageName()))
        {
            // Filter out the missing files.
            return;
        }

        if (reference.IsPart())
        {
            DumpPartReference(reference, transformList, referenceEntityCache, jsonData);
        }
        else
        {
            int numberInstances = reference.GetInstancesCount();
            for (int i = 0; i < numberInstances; i++)
            {
                std::vector<SPAtransf> childTransformList = transformList;
                auto childInstance = reference.GetInstance(i);
                auto childReference = childInstance.GetReference();
                double oAffineTransform[3][3] = {0};
                double oTranslation[3] = {0};
                childInstance.GetInstancePositionMatrix(oAffineTransform, oTranslation);
                SPAtransf transf = InterOp::GenerateTransform(oAffineTransform, oTranslation);
                childTransformList.push_back(transf);
                DumpReference(childReference, transformList, missingFileIter, jsonData);
            }
        }
    }
}

namespace InterOp
{

    MetaDataExporter::MetaDataExporter(const std::string &outJsonFilePath, FacetOptions facetOptions) 
        : m_facetOptions(facetOptions), m_outJsonFilePath(outJsonFilePath)
    {
    }

    bool MetaDataExporter::Export(const SPAIopPSImportResult &importResult, const SPAIopInputProductStructure &inputProductStructure, MetaDataInfo &info)
    {
        // Initialize export data.
        // Topology json.
        json productStructJson;
        m_area = .0;
        m_volume = .0;
        m_vertexes.clear();
        m_triangles.clear();
        m_inputFacetOptions = m_facetOptions;

        // Get missing files.
        auto missingFileIter = importResult.GetMissingFileIterator();
        int numberRootInstances = inputProductStructure.GetRootInstancesCount();
        for (int i = 0; i < numberRootInstances; i++)
        {
            auto inputRootInstance = inputProductStructure.GetRootInstance(i);
            double oAffineTransform[3][3] = {0};
            double oTranslation[3] = {0};
            inputRootInstance.GetInstancePositionMatrix(oAffineTransform, oTranslation);
            SPAtransf transf = InterOp::GenerateTransform(oAffineTransform, oTranslation);
            std::vector<SPAtransf> transformList;
            transformList.push_back(transf);
            auto reference = inputRootInstance.GetReference();
            json referenceJson = json::object();
            DumpReference(reference, transformList, missingFileIter, referenceJson);
            if(!referenceJson.empty())
            {
                productStructJson["details"].push_back(referenceJson);
            }
        }
        std::ofstream outFile(m_outJsonFilePath);
        if (outFile.is_open())
        {
            outFile << productStructJson.dump(2); // Indent 2
            outFile.close();
            LOG_INFO("Dump topology data to file: {}", m_outJsonFilePath);
        }
        else
        {
            LOG_INFO("Failed to Dump topology data");
        }

        info.area = m_area;
        info.volume = m_volume;
        info.vertexes = m_vertexes;
        info.triangles = m_triangles;
        info.topologyJsonFilePath = m_outJsonFilePath;

        return true;
    }
}

AMCAX_EXCHANGE_NAMESPACE_END